﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using DynORM.Interfaces;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml;
using DynORM.TypeMappingSubsystem;

namespace DynORM.ValueObjects
{
    [XmlRoot(ElementName = PrimaryKeyDescriptor.PrimaryKeyDescriptorTag)]
    public class PrimaryKeyDescriptor : IPrimaryKeyDescriptor, IXmlSerializable
    {
        private RelationType relationType; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer
        private string tableName; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer
        private Dictionary<int, IFieldDescriptor> fieldDescriptors; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer
        private string name; //had to relax "readonly" restriction of this member in favor to init logic of xml serializer

        //for serializer
        private PrimaryKeyDescriptor()
        {
            this.relationType = RelationType.PrimaryKey;
            this.fieldDescriptors = new Dictionary<int, IFieldDescriptor>();
        }

        public const string PrimaryKeyDescriptorTag = "PrimaryKeyDescriptor";
        public const string PrimaryKeyDescriptorNameAttr = "Name";
        public const string PrimaryKeyDescriptorTableNameAttr = "TableName";
        public const string PrimaryKeyDescriptorFieldsTag = "PrimaryKeyDescriptorFields";

        public PrimaryKeyDescriptor(string tableName, IDictionary<int, IFieldDescriptor> fieldDescriptors, string name)
        {
            this.relationType = RelationType.PrimaryKey;
            this.fieldDescriptors = new Dictionary<int, IFieldDescriptor>();

            foreach (KeyValuePair<int, IFieldDescriptor> fd in fieldDescriptors)
                this.fieldDescriptors.Add(fd.Key, fd.Value);

            this.tableName = tableName ?? String.Empty;

            this.name = name;
        }

        public string Name
        {
            get
            {
                return this.name;
            }
        }

        public RelationType RelationType
        {
            get { return this.relationType; }
        }

        public string TableName
        {
            get
            {
                return this.tableName;
            }
        }

        public IDictionary<int, IFieldDescriptor> FieldDescriptors
        {
            get
            {
                return this.fieldDescriptors;
            }
        }

        #region Predefined conventions
        private static Type predefinedTypeForIDs = typeof(Guid);

        public static Type PredefinedTypeForIDs
        {
            get { return predefinedTypeForIDs; }
            set { predefinedTypeForIDs = value; }
        }

        private static string predefinedFieldNameForIDs = "RowID";

        public static string PredefinedFieldNameForIDs
        {
            get { return predefinedFieldNameForIDs; }
            set { predefinedFieldNameForIDs = value; }
        }
        
        #endregion

        public CheckIntegrityResult CheckStructureIntegrity(bool usePredefinedConvention)
        {
            CheckIntegrityResult passed = new CheckIntegrityResult(true, String.Empty);
            foreach (KeyValuePair<int, IFieldDescriptor> fd in this.fieldDescriptors)
            {
                if (fd.Value.CheckStructureIntegrity(false) != passed)
                    return new CheckIntegrityResult(false, "One field failed its structure check. See inner CheckIntegrityResult.", fd.Value.CheckStructureIntegrity(false));

                if (usePredefinedConvention)
                {
                    if (!(fd.Value.FieldType == predefinedTypeForIDs || fd.Value.FieldType == Types.GetCLRNullableType(predefinedTypeForIDs)))
                        return new CheckIntegrityResult(false, String.Format("Field type must be {0}.", predefinedTypeForIDs.Name));

                    if (!Regex.IsMatch(fd.Value.FieldName, String.Format(@"{0}.*", predefinedFieldNameForIDs)))
                        return new CheckIntegrityResult(false, String.Format("Field name must be {0} (or {0}_n in case of composite key).", predefinedFieldNameForIDs));
                }
            }
            return passed;
        }

        #region Value-based equality semantics implementation
        public override bool Equals(object obj)
        {
            //PrimaryKeyDescriptor that = obj as PrimaryKeyDescriptor;
            //if (Object.ReferenceEquals(that, null))
            if (!(obj is PrimaryKeyDescriptor))
                return false;
            PrimaryKeyDescriptor that = (PrimaryKeyDescriptor)obj;

            if (this.FieldDescriptors.Count<KeyValuePair<int, IFieldDescriptor>>() != that.FieldDescriptors.Count<KeyValuePair<int, IFieldDescriptor>>())
                return false;

            for (int i = 0; i < this.FieldDescriptors.Count; i++ )
            {
                FieldDescriptor thisFD = (FieldDescriptor)this.FieldDescriptors[i];
                FieldDescriptor thatFD = (FieldDescriptor)that.FieldDescriptors[i];
                if (thisFD != thatFD) return false;
            }

            if (String.Compare(this.TableName, that.TableName, StringComparison.OrdinalIgnoreCase) != 0)
                return false;
            else
                return true;
        }

        public override int GetHashCode()
        {
            return this.TableName.GetHashCode();
        }

        public static bool operator ==(PrimaryKeyDescriptor rd1, IRelationDescriptor rd2)
        {
            if (Object.ReferenceEquals(rd1, null))
                return false;

            return rd1.Equals(rd2);
        }

        public static bool operator !=(PrimaryKeyDescriptor rd1, IRelationDescriptor rd2)
        {
            if (Object.ReferenceEquals(rd1, null))
                return false;

            return !rd1.Equals(rd2);
        }

        #endregion

        #region IXmlSerializable and auxiliary CreateFromXml member
        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            int i = 0;
            do
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        {
                            if (string.Compare(reader.Name, PrimaryKeyDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                bool emptyElement = reader.IsEmptyElement;
                                if (reader.HasAttributes)
                                {
                                    while (reader.MoveToNextAttribute())
                                    {
                                        //if (string.Compare(reader.Name, PrimaryKeyDescriptorUniqueNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                        //    this.uniqueName = reader.Value;
                                        if (string.Compare(reader.Name, PrimaryKeyDescriptorNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            this.name = reader.Value;
                                        else if (string.Compare(reader.Name, PrimaryKeyDescriptorTableNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            this.tableName = reader.Value;
                                    }
                                }
                                if (emptyElement)
                                    return;
                            }
                            else if (string.Compare(reader.Name, PrimaryKeyDescriptorFieldsTag, StringComparison.OrdinalIgnoreCase) == 0)
                            { }
                            else if (string.Compare(reader.Name, FieldDescriptor.FieldDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                FieldDescriptor fd = FieldDescriptor.CreateFromXml(reader);
                                this.fieldDescriptors.Add(i, fd);
                                i++;
                            }
                        } break;
                    case XmlNodeType.EndElement:
                        {
                            if (string.Compare(reader.Name, PrimaryKeyDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                                return;
                        } break;
                    default: continue;
                }
            } while (reader.Read());
        }

        public static PrimaryKeyDescriptor CreateFromXml(XmlReader reader)
        {
            //string uniqueName = String.Empty;
            string name = String.Empty;
            string tableName = String.Empty;
            Dictionary<int, IFieldDescriptor> fieldDescriptors = new Dictionary<int, IFieldDescriptor>();
            int i = 0;

            do
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        {
                            if (string.Compare(reader.Name, PrimaryKeyDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                bool emptyElement = reader.IsEmptyElement;
                                if (reader.HasAttributes)
                                {
                                    while (reader.MoveToNextAttribute())
                                    {
                                        //if (string.Compare(reader.Name, PrimaryKeyDescriptorUniqueNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                        //    uniqueName = reader.Value;
                                        if (string.Compare(reader.Name, PrimaryKeyDescriptorNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            name = reader.Value;
                                        else if (string.Compare(reader.Name, PrimaryKeyDescriptorTableNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            tableName = reader.Value;
                                    }
                                }
                                if (emptyElement)
                                    return new PrimaryKeyDescriptor(tableName, fieldDescriptors, name);
                            }
                            else if (string.Compare(reader.Name, PrimaryKeyDescriptorFieldsTag, StringComparison.OrdinalIgnoreCase) == 0)
                            { }
                            else if (string.Compare(reader.Name, FieldDescriptor.FieldDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                FieldDescriptor fd = FieldDescriptor.CreateFromXml(reader);
                                fieldDescriptors.Add(i, fd);
                                i++;
                            }
                        } break;
                    case XmlNodeType.EndElement:
                        {
                            if (string.Compare(reader.Name, PrimaryKeyDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                                return new PrimaryKeyDescriptor(tableName, fieldDescriptors, name); 
                        } break;
                    default: continue;
                }
            } while (reader.Read());
            return new PrimaryKeyDescriptor(tableName, fieldDescriptors, name);
        }

        public void WriteXml(XmlWriter writer)
        {
            //writer.WriteAttributeString(PrimaryKeyDescriptorUniqueNameAttr, this.UniqueName);
            writer.WriteAttributeString(PrimaryKeyDescriptorTableNameAttr, this.TableName);
            writer.WriteAttributeString(PrimaryKeyDescriptorNameAttr, this.Name);
            if (this.FieldDescriptors.Count() > 0)
            {
                writer.WriteStartElement(PrimaryKeyDescriptorFieldsTag);
                foreach (KeyValuePair<int, IFieldDescriptor> f in this.FieldDescriptors.OrderBy<KeyValuePair<int, IFieldDescriptor>, int>(kv => kv.Key))
                {
                    writer.WriteStartElement(FieldDescriptor.FieldDescriptorTag);
                    ((FieldDescriptor)f.Value).WriteXml(writer);
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }
        }
        #endregion    

    }

}
